local http = require "http_plugin.http"
local websocket = require "http_plugin.websocket"
local STRING_SUB = string.sub
local TABLE_INSERT = table.insert
local TABLE_REMOVE = table.remove

local controller = {
    router = nil,
    run_count = 1,
    ran_count = 0,
    SocketToConnect = {},
    SessionidToConnect = {},
    ConnectionCount = 0
}

-- local connnect_struct = {
--     hsock = false,       -- socket句柄
--     close = false,       -- 接受返回后是否需要关闭连接
--     ssl = false,         -- ssl连接
--     websocket = false,   -- websocket连接

--     on_response = false,     -- 接口回调
--     on_ws_request = false,   -- websocket前调
--     on_ws_response = false,  -- websocket回调
--     on_close = false,        -- socket连接关闭回调

--     reqs = {},       -- 请求队列
--     res = {},        -- http返回体
--     req_stream = "", -- http请求字节流
--     res_stream = "", -- http返回字节流
--     req_time = 0,    -- 请求时间
--     res_time = 0     -- 返回时间
-- }

function controller.init(router, play_fast, run_count)
    controller.router = router
    controller.run_count = run_count
    PlayFast(play_fast)
    PlayNoStop()  --用例运行到最后一步就会自动停止，防止还有请求没有返回就结
end

function controller.timeout()
    if PlayOver() and controller.ConnectionCount == 0 then
        --这里要小心处理，执行次数+1，尚有剩余次数则回到第一步用例重新执行
        controller.ran_count = controller.ran_count + 1
        if controller.run_count > controller.ran_count then
            PlayBack(0)  --回退用例步骤0开始执行
        end
        PlayNormal()
    end
end

function controller.connect_close(hsock, msg)
    local conn = controller.SocketToConnect[hsock]
    if conn then
        controller.SocketToConnect[hsock] = nil
        controller.ConnectionCount = controller.ConnectionCount - 1
        if conn.on_close then
            conn.on_close(conn, msg)
        end
        SocketClose(hsock)
    end
end

function controller.connection_open(ip, port, protocol)
    local connect_key = PlayStepSession()
    conn = {req_stream="", reqs = {}}
    controller.SessionidToConnect[connect_key] = conn
end

function controller.connection_close(ip, port, protocol)
    local connect_key = PlayStepSession()
    local conn = controller.SessionidToConnect[connect_key]
    if conn then
        conn.close = true
        controller.SessionidToConnect[connect_key] = nil
        if conn.websocket or (conn.req_time == 0 and conn.res_time == 0) then
            controller.connect_close(conn.hsock, nil)
        end
    end
end

function controller.connection_made(hsock)
    local conn = controller.SocketToConnect[hsock]
    if conn then
        conn.res_time = Microsecond()
        local response_time = conn.res_time - conn.req_time
        local req = conn.reqs[1]
        ReportAPIResponse("connect "..req.ip..":"..req.port, math.ceil(response_time/1000))
        local package = http.request_encode(req)
        conn.req_time = Microsecond()
        conn.res_time = 0
        SocketSend(hsock, package)
        ReportAPISend(req.method.." "..req.uri, #package, 1)
        ReportCounter("RPS", 1, 1, 1)
    end
end

function controller.connection_failed(hsock)
    ip, port = SocketPeerAddrGet(hsock)
    Log(LOG_ERROR, "Connect faile", ip, port)
    local conn = controller.SocketToConnect[hsock]
    if conn then
        local req = conn.reqs[1]
        ReportAPIResponse(conn.req.method.." "..req.uri, -1)
        ReportCounter("ERROR", 1, 1, 1)
        if conn.res_callback then
            conn.res_callback(http)
        end
        controller.connect_close(hsock, "连接失败")
    end
end

function controller.connection_closed(hsock, err)
    controller.connect_close(hsock, "对端关闭连接")
end

function controller.connection_recved(hsock, data)
    local conn = controller.SocketToConnect[hsock]
    if conn then
        conn.res_stream = conn.res_stream..data
        if conn.websocket then
            return controller.websocket_response(conn)
        end
        local req = conn.reqs[1]
        local ret = http.response_decode(conn.res_stream, conn.res, req)
        if ret > 0 then
            conn.res_stream = STRING_SUB(conn.res_stream, ret + 1)
            conn.res_time = Microsecond()
            local response_time = math.ceil((conn.res_time - conn.req_time)/1000)
            ReportCounter("TPS", 1, 1, 1)
            ReportAPIRecv(req.method.." "..req.uri, ret, 1)
            ReportAPIResponse(req.method.." "..req.uri, response_time)
            if  conn.res.status_code == 101 then
                conn.websocket = true
            end
            Log(LOG_NORMAL, req.method.." "..req.host..req.uri, conn.res.status_code, conn.res.status, response_time, "ms")
            if req.on_response then
                req.on_response(conn, conn.res)
            end

            TABLE_REMOVE(conn.reqs, 1)
            conn.req_time = 0
            conn.res_time = 0
            if conn.close then
                controller.connect_close(hsock, nil)
            else
                req = conn.reqs[1]
                if req then
                    local package = http.request_encode(req)
                    conn.req_time = Microsecond()
                    SocketSend(hsock, package)
                    ReportCounter("RPS", 1, 1, 1)
                end
            end
        elseif ret < 0 then
            LOG(3, "http response error")
            controller.connect_close(hsock, "http返回解析错误")
        end
    end
end

function controller.request_router(conn, req)
    local router = controller.router
    local call = router[req.uri]
	if call ~= nil then
		call(conn, req)
    else
        call = router["*"]
        if call ~= nil then
            call(conn, req)
        end
	end
end

function controller.connection_send(ip, port, data, protocol)
    if protocol == 8 or protocol == 9 then
		controller.http_short_request(ip, port, data, protocol)
	else
		controller.http_long_request(ip, port, data, protocol)
	end
end

function controller.http_short_request(ip, port, data, protocol)
    local req = {}
	req.header = {}
    req.host = ip
    req.port = port
	local ret = http.request_decode(data, req)
    if ret > 0 then
        local conn = {}
        if protocol == 9 then conn.ssl = true end
        conn.close = true
        conn.reqs = {}
        conn.res = {}
        conn.res_callback = false
        conn.res_stream = ""

        controller.request_router(conn, req)
        req.on_response = conn.on_response
        TABLE_INSERT(conn.reqs, req)
        
        if conn.ssl then protocol = 2 else protocol = 0 end
        req.ip = GetHostByName(req.host)
        conn.req_time = Microsecond()
        conn.hsock = SocketConnect(req.ip, req.port, protocol)
        if conn.hsock then
            controller.SocketToConnect[conn.hsock] = conn
            controller.ConnectionCount = controller.ConnectionCount + 1
        else
            Log(3, "Socket Create Faile")
        end
    else
        Log(3, "event unpcak error")
    end
end

function controller.http_long_request(ip, port, data, protocol)
    local connect_key = PlayStepSession()
    local conn = controller.SessionidToConnect[connect_key]
    if conn == nil then 
        Log(3, "can not find case sessionid")
        return
    end
    conn.req_stream = conn.req_stream..data
    if conn.websocket then
        return controller.websocket_request(conn)
    end
    local req = {}
	req.header = {}
    req.host = ip
    req.port = port
    local ret = http.request_decode(conn.req_stream, req)
    if ret then
        conn.req_stream = STRING_SUB(conn.req_stream, ret + 1)
        conn.close = false
        conn.res = {}
        conn.res_callback = false
        conn.res_stream = ""

        controller.request_router(conn, req)
        req.on_response = conn.on_response
        TABLE_INSERT(conn.reqs, req)

        if conn.ssl then protocol = 2 else protocol = 0 end
        if conn.hsock then
            local package = http.request_encode(conn.req)
            conn.req_time = Microsecond()
            SocketSend(hsock, package)
            ReportCounter("RPS", 1, 1, 1)
        else
            req.ip = GetHostByName(req.host)
            conn.req_time = Microsecond()
            conn.hsock = SocketConnect(req.ip, req.port, protocol)
            if conn.hsock then
                controller.SocketToConnect[conn.hsock] = conn
                controller.ConnectionCount = controller.ConnectionCount + 1
            else
                Log(3, "Socket Create Faile")
            end
        end
    else
        Log(3, "event unpcak error")
    end
end

function controller.websocket_request(conn)
    local encoded = conn.req_stream
    while true do
        local decoded, fin, opcode, mask, encoded_backup = websocket.decode(encoded)
        if decoded then
            encoded = encoded_backup
            if conn.on_ws_request and #decoded > 0 then
                decoded = conn.on_ws_request(conn, decoded)
            end
            if (decoded and #decoded > 0) then
                local package = websocket.encode(decoded, opcode, mask, fin)
                SocketSend(conn.hsock, package)
            end
        else
            break
        end
    end
    conn.req_stream = encoded
end

function controller.websocket_response(conn)
    local encoded = conn.res_stream
    while true do
        local decoded, fin, opcode, mask, encoded_backup = websocket.decode(encoded)
        if decoded then
            encoded = encoded_backup
            if conn.on_ws_response and #decoded > 0 then
                conn.on_ws_response(conn, decoded)
            end
        else
            break
        end
    end
    conn.res_stream = encoded
end

return controller